<?php
/**
 * Image
 * 
 * 单例模式
 * 图片处理类，包含生成验证码，缩略图，图片水印，文字水印
 *
 * @author   songhuan <iphper@yeah.net>
 * @version   $Id: Image.php 1 2010-08-10 14:40:00Z $
 */
class Image 
{
	/**
	 * @var string 加载图片函数名称 imagecreatefromjpeg | imagecreatefrompng | imagecreatefromgif | imagecreatefromwbmp
	 */
	private $_loaderfunc = '';
	
	/**
	 * @var $_creatorfunc 创建图片函数名称  imagejpeg | imagepng | imagegif | imagewbmp
	 */
	private $_creatorfunc = '';
	
	/**
	 * @var string 图片MIME
	 */
	private $_mime = '';
	
	/**
	 * @var int 图片宽度
	 */
	private $_sourceWidth = 0;
	
	/**
	 * @var int 图片高度
	 */
	private $_sourceHeight = 0;
	
	/**
	 * @var int 水印图片距离原图片左上角横坐标位置
	 */
	private $_posX = 0;
	
	/**
	 * @var int 水印图片距离原图片左上角纵坐标位置
	 */
	private $_posY = 0;
	
	private static $_instance = null;
	
	private function __construct()
	{
	}
	
	private function __clone()
	{
	}
	
	public static function getInstance()
	{
		if (null === self::$_instance) 
			self::$_instance = new self();
		
		return self::$_instance;
	}
	
	/**
	 * 验证码
	 * 
	 * @param int $width
	 * @param int $height
	 * @param int $len 验证码字符长度
	 * @param string $format: ALL(英文字母加数字),LETTER(字母),NUMBER(数字)
	 * @return void
	 */
	public function verify($width = 160, $height = 50, $len = 6, $format = 'ALL')
	{
		$strs = $this->_randStr($len, $format);
		
		@header('Content-Type: image/png');
		$im = imagecreatetruecolor($width, $height);
		
		$bgcolor = imagecolorallocate($im, mt_rand(0, 155), mt_rand(0, 155), mt_rand(0, 155));
		imagefill($im, 0, 0, $bgcolor);

		$fbcolor = imagecolorallocate($im, mt_rand(156, 255), mt_rand(156, 255), mt_rand(156, 255));
		$fontfile = LANG_DIR . DS . 'fonts' . DS . V::$config['web']['fontfile'];
		$midheight = $height/2;
		foreach ($strs as $i => $str)
		{
			$x = $i * 21 + 20;
			$y = $i % 2 === 0 ? 4 : 15;
			$angle = mt_rand(0, 1) === 1 ? 15 : -15;
			imagettftext($im, 18, $angle, $x, $midheight + $y, $fbcolor, $fontfile, $str);
		}
		
		imagerectangle($im, 0, 0, $width-1, $height-1, $fbcolor);
		imagerectangle($im, 1, 1, $width-2, $height-2, $fbcolor);
		
		/*
		$lines = array();
		$lineheight = 2;
		$lines[] = array(mt_rand($lineheight, $width/2), $height/2, mt_rand($width/2, $width-$lineheight), $height/2); //粗横线
		$lines[] = array(mt_rand($lineheight, $width/2), mt_rand($height/2, $height-$lineheight), mt_rand($width/2, $width-$lineheight), mt_rand($lineheight, $height/2)); //上斜线
		$lines[] = array(mt_rand($lineheight, $width/2), mt_rand($lineheight, $height/2), mt_rand($width/2, $width-$lineheight), mt_rand($height/2, $height-$lineheight)); //下斜线
		foreach ($lines as $line)
		{
			imagesetthickness($im, $lineheight);
			$linecolor = imagecolorallocate($im, mt_rand(150, 255), mt_rand(150, 255), mt_rand(150, 255));
			imageline($im, $line[0], $line[1], $line[2], $line[3], $linecolor);
		}
		*/
		
		for ($i=0; $i<60; $i++)
		{
			$pixcolor = imagecolorallocate($im, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
			imagesetpixel($im, mt_rand(3, $width-3), mt_rand(3, $height-3), $pixcolor);
		}
		
		imagepng($im);
		imagedestroy($im);
	}
	
	/**
	 * 随机字符串
	 * 
	 * @param string $format: ALL(英文字母加数字),LETTER(字母),NUMBER(数字)
	 * @return array 随机字符串
	 */
	private function _randStr($len = 4, $format = 'ALL') 
	{
		$chars = 'ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789'; 
		$start = $format=='NUMBER' ? 49 : 0;
		$end = $format=='LATTER' ? 48 : 56;
		$strs = array();
		for ($i=0; $i<$len; $i++)
			$strs[] = $chars{mt_rand($start, $end)};
		
		$_SESSION['verifycodes'] = md5(strtoupper(implode('', $strs)));
		return $strs;
	}
	
	/**
	 * 缩略图
	 * 
	 * @param string $source 原图片地址
	 * @param string $thumb 生成图片地址
	 * @param int $thumbWidth
	 * @param int $thumbHeight
	 * @param boolean $scale 是否按比例缩小
	 * @param boolean $inflate 太小的图像，是否放大它们以填充缩略图
	 * @return void
	 */
	public function thumbnail($sourcePath, $toPath = null, $thumbWidth, $thumbHeight, $scale = true, $inflate = true)
	{
		$source = $this->_load($sourcePath);
		
		if ($this->_sourceWidth <= $thumbWidth && $this->_sourceHeight <= $thumbHeight && !$inflate)
			$thumb = $source;
		else 
		{
			if ($scale)
			{
				if ($this->_sourceWidth > $this->_sourceHeight)
					$thumbHeight = floor($this->_sourceHeight * ($thumbWidth/$this->_sourceWidth));
				else if ($this->_sourceWidth < $this->_sourceHeight)
					$thumbWidth = floor($this->_sourceWidth * ($thumbHeight/$this->_sourceHeight));
			}
			
			$thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);
			imagecopyresampled($thumb, $source, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $this->_sourceWidth, $this->_sourceHeight);
		}
		
		return $this->_build($thumb, $toPath);
	}
	
	/**
	 * 文字水印
	 * 
	 * @param string $sourcePath 需要加水印的图片地址
	 * @param string $text 水印文字 
	 * @param string $toPath 新图片生成地址
	 * @param int $position 图片位置
	 * 1: 顶端居左      2: 顶端居中      3: 顶端居右
	 * 4: 中间居左      5: 中间居中      6: 中间居右
	 * 7: 下端居左      8: 下端居中      9: 下端居右
	 * @return void
	 */
	public function textWater($sourcePath, $text, $toPath = null, $position = 9)
	{
		$source = $this->_load($sourcePath);
		
		$fontfile = LANG_DIR . DS . 'fonts' . DS . 'PilsenPlakat.ttf';
		$box = imagettfbbox(24, 0, $fontfile, $text);
		$min_x = min(array($box[0], $box[2], $box[4], $box[6]));
		$max_x = max(array($box[0], $box[2], $box[4], $box[6]));
		$min_y = min(array($box[1], $box[3], $box[5], $box[7]));
		$max_y = max(array($box[1], $box[3], $box[5], $box[7]));
		$waterWidth = $max_x - $min_x;
		$waterHeight = $max_y - $min_y;
		$this->_calculatePosition($waterWidth, $waterHeight, $position);
		
		$fontcolor = imagecolorallocate($source, 255, 0, 0);
		imagettftext($source, 24, 0, $this->_posX, $this->_posY + $waterHeight, $fontcolor, $fontfile, $text);
		
		return $this->_build($source, $toPath);
	}
	
	/**
	 * 图片水印
	 * 
	 * @param string $sourcePath 需要加水印的图片地址
	 * @param string $waterPath 水印图片地址 
	 * @param string $toPath 新图片生成地址
	 * @param int $position 图片位置
	 * 1: 顶端居左      2: 顶端居中      3: 顶端居右
	 * 4: 中间居左      5: 中间居中      6: 中间居右
	 * 7: 下端居左      8: 下端居中      9: 下端居右
	 * @return void
	 */
	public function imageWater($sourcePath, $waterPath, $toPath = null, $position = 9)
	{
		$water = $this->_load($waterPath);
		$waterWidth = $this->_sourceWidth;
		$waterHeight = $this->_sourceHeight;
		$source = $this->_load($sourcePath);
		$this->_calculatePosition($waterWidth, $waterHeight, $position);

		imagecopymerge($source, $water, $this->_posX, $this->_posY, 0, 0, $waterWidth, $waterHeight, 100);
		
		return $this->_build($source, $toPath);
	}
	
	/**
	 * 计算水印在图片中的位置
	 * 
	 * @param int $waterWidth
	 * @param int $waterHeight
	 * @param int $position
	 * @return void
	 */
	private function _calculatePosition($waterWidth, $waterHeight, $position)
	{
		switch ($position) 
		{
			case 1: //顶端居左
				$this->_posX = 5;
				$this->_posY = 5;
				break;
			case 2: //顶端居中
				$this->_posX = ($this->_sourceWidth - $waterWidth) / 2;
				$this->_posY = 5;
				break;
			case 3: //顶端居右
				$this->_posX = $this->_sourceWidth - $waterWidth - 5;
				$this->_posY = 5;
				break;
			case 4: //中间居左
				$this->_posX = 5;
				$this->_posY = ($this->_sourceHeight - $waterHeight) / 2;
				break;
			case 5: //中间居中
				$this->_posX = ($this->_sourceWidth - $waterWidth) / 2;
				$this->_posY = ($this->_sourceHeight - $waterHeight) / 2;
				break;
			case 6: //中间居右
				$this->_posX = $this->_sourceWidth - $waterWidth - 5;
				$this->_posY = ($this->_sourceHeight - $waterHeight) / 2;
				break;
			case 7: //下端居左
				$this->_posX = 5;
				$this->_posY = $this->_sourceHeight - $waterHeight - 5;
				break;
			case 8: //下端居中
				$this->_posX = ($this->_sourceWidth - $waterWidth) / 2;
				$this->_posY = $this->_sourceHeight - $waterHeight - 5;
				break;
			case 9: //下端居右
			default: 
				$this->_posX = $this->_sourceWidth - $waterWidth - 5;
				$this->_posY = $this->_sourceHeight - $waterHeight - 5;
				break;
		}
	}
	
	/**
	 * 加载并获取图片资源
	 * 
	 * @param string $sourcePath
	 * @return resource 图片资源
	 */
	private function _load($sourcePath)
	{
		if (!$dims = @getimagesize($sourcePath))
			throw new Exception('Count not find image: ' . $sourcePath);
		
		$this->_mime = $dims['mime']; 
		switch ($this->_mime)
		{
			case 'image/jpeg' : 
			case 'image/pjpeg': 
				$this->_loaderfunc = 'imagecreatefromjpeg';
				$this->_creatorfunc = 'imagejpeg';
				break;
			case 'image/png'  : 
			case 'image/xpng' :	
				$this->_loaderfunc = 'imagecreatefrompng';
				$this->_creatorfunc = 'imagepng';
				break;
			case 'image/gif'  :	
				$this->_loaderfunc = 'imagecreatefromgif';
				$this->_creatorfunc = 'imagegif';
				break;
			case 'image/bmp'  : 
			case 'image/wbmp' : 
				$this->_loaderfunc = 'imagecreatefromwbmp';
				$this->_creatorfunc = 'imagewbmp';
				break;
			default : throw new Exception('Image MIME type ' . $this->_mime . ' not supported'); 
		}
		
		list($this->_sourceWidth, $this->_sourceHeight) = $dims;
		
		$loaderfunc = $this->_loaderfunc;
		return $loaderfunc($sourcePath);
	}
	
	/**
	 * 输出图片
	 * 
	 * @param resource $source
	 * @param string $toPath 新图片生成地址，如果为空则浏览器输出
	 * @return boolean
	 */
	private function _build($source, $toPath = null)
	{
		$creatorfunc = $this->_creatorfunc;
		if (null !== $toPath) 
			 return $creatorfunc($source, $toPath);
		else 
		{
			@header('Content-Type: ' . $this->_mime);
			return $creatorfunc($source);
		}
	}
}
